// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Runtime.Serialization; using System.Xml.Linq; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.CodeStyle; /// <summary> /// Internal representation of a code style option value. Should be used throughout Roslyn. /// The internal values are translated to the public ones (ICodeStyleOption) at the public entry points. /// </summary> internal interface ICodeStyleOption2 { XElement ToXElement(); object? Value { get; } NotificationOption2 Notification { get; } ICodeStyleOption2 WithValue(object value); ICodeStyleOption2 WithNotification(NotificationOption2 notification); /// <summary> /// Creates a new <see cref="ICodeStyleOption2"/> from a specified <paramref name="element"/>. /// </summary> /// <exception cref="Exception"> /// The type of the serialized data does not match the type of <see cref="ICodeStyleOption2.Value"/> or the format of the serialized data is invalid. /// </exception> ICodeStyleOption2 FromXElement(XElement element); } internal static class CodeStyleOption2 { /// <remarks> /// When user preferences are not yet set for a style, we fall back to the default value. /// One such default(s), is that the feature is turned on, so that codegen consumes it, /// but with silent enforcement, so that the user is not prompted about their usage. /// </remarks> public static readonly CodeStyleOption2<bool> TrueWithSilentEnforcement = new(value: true, notification: NotificationOption2.Silent); public static readonly CodeStyleOption2<bool> FalseWithSilentEnforcement = new(value: false, notification: NotificationOption2.Silent); public static readonly CodeStyleOption2<bool> TrueWithSuggestionEnforcement = new(value: true, notification: NotificationOption2.Suggestion); public static readonly CodeStyleOption2<bool> FalseWithSuggestionEnforcement = new(value: false, notification: NotificationOption2.Suggestion); /// <summary> /// Use singletons for most common values. /// </summary> public static CodeStyleOption2<bool> GetCodeStyle(bool value, NotificationOption2 notification) => (value, notification.Severity) switch { (true, ReportDiagnostic.Hidden) => TrueWithSilentEnforcement, (true, ReportDiagnostic.Info) => TrueWithSuggestionEnforcement, (false, ReportDiagnostic.Hidden) => FalseWithSilentEnforcement, (false, ReportDiagnostic.Info) => FalseWithSuggestionEnforcement, _ => new(value, notification) }; } /// <summary> /// Represents a code style option and an associated notification option. Supports /// being instantiated with T as a <see cref="bool"/> or an <c>enum type</c>. /// /// CodeStyleOption also has some basic support for migration a <see cref="bool"/> option /// forward to an <c>enum type</c> option. Specifically, if a previously serialized /// bool-CodeStyleOption is then deserialized into an enum-CodeStyleOption then 'false' /// values will be migrated to have the 0-value of the enum, and 'true' values will be /// migrated to have the 1-value of the enum. /// /// Similarly, enum-type code options will serialize out in a way that is compatible with /// hosts that expect the value to be a boolean. Specifically, if the enum value is 0 or 1 /// then those values will write back as false/true. /// </summary> [DataContract] internal sealed partial class CodeStyleOption2<T>(T value, NotificationOption2 notification) : ICodeStyleOption2, IEquatable<CodeStyleOption2<T>?> { public static readonly CodeStyleOption2<T> Default = new(default!, NotificationOption2.Silent); private const int SerializationVersion = 1; private const string XmlElement_CodeStyleOption = "CodeStyleOption"; private const string XmlAttribute_SerializationVersion = "SerializationVersion"; private const string XmlAttribute_Type = "Type"; private const string XmlAttribute_Value = "Value"; private const string XmlAttribute_DiagnosticSeverity = "DiagnosticSeverity"; [DataMember(Order = 0)] public T Value { get; } = value; [DataMember(Order = 1)] public NotificationOption2 Notification { get; } = notification; object? ICodeStyleOption2.Value => this.Value; ICodeStyleOption2 ICodeStyleOption2.WithValue(object value) => WithValue((T)value); ICodeStyleOption2 ICodeStyleOption2.WithNotification(NotificationOption2 notification) => new CodeStyleOption2<T>(Value, notification); public CodeStyleOption2<T> WithValue(T value) { if (typeof(T) == typeof(bool)) { var boolValue = (bool)(object)value!; if (boolValue == (bool)(object)Value!) { return this; } return (CodeStyleOption2<T>)(object)CodeStyleOption2.GetCodeStyle(boolValue, Notification); } return EqualityComparer<T>.Default.Equals(value, Value) ? this : new CodeStyleOption2<T>(value, Notification); } private int EnumValueAsInt32 => (int)(object)Value!; public XElement ToXElement() => new(XmlElement_CodeStyleOption, // Ensure that we use "CodeStyleOption" as the name for back compat. new XAttribute(XmlAttribute_SerializationVersion, SerializationVersion), new XAttribute(XmlAttribute_Type, GetTypeNameForSerialization()), new XAttribute(XmlAttribute_Value, GetValueForSerialization()), new XAttribute(XmlAttribute_DiagnosticSeverity, Notification.Severity.ToDiagnosticSeverity() ?? DiagnosticSeverity.Hidden)); private object GetValueForSerialization() { if (typeof(T) == typeof(string)) { return Value!; } else if (typeof(T) == typeof(bool)) { return Value!; } else if (IsZeroOrOneValueOfEnum()) { return EnumValueAsInt32 == 1; } else { return EnumValueAsInt32; } } private string GetTypeNameForSerialization() { if (typeof(T) == typeof(string)) { return nameof(String); } if (typeof(T) == typeof(bool) || IsZeroOrOneValueOfEnum()) { return nameof(Boolean); } else { return nameof(Int32); } } private bool IsZeroOrOneValueOfEnum() { var intVal = EnumValueAsInt32; return intVal is 0 or 1; } ICodeStyleOption2 ICodeStyleOption2.FromXElement(XElement element) => FromXElement(element); public static CodeStyleOption2<T> FromXElement(XElement element) { var typeAttribute = element.Attribute(XmlAttribute_Type); var valueAttribute = element.Attribute(XmlAttribute_Value); var severityAttribute = element.Attribute(XmlAttribute_DiagnosticSeverity); var version = (int?)element.Attribute(XmlAttribute_SerializationVersion); if (typeAttribute == null || valueAttribute == null || severityAttribute == null) { // data from storage is corrupt, or nothing has been stored yet. return Default; } if (version != SerializationVersion) { return Default; } var parser = GetParser(typeAttribute.Value); var value = parser(valueAttribute.Value); var severity = (DiagnosticSeverity)Enum.Parse(typeof(DiagnosticSeverity), severityAttribute.Value); return new CodeStyleOption2<T>(value, severity switch { DiagnosticSeverity.Hidden => NotificationOption2.Silent, DiagnosticSeverity.Info => NotificationOption2.Suggestion, DiagnosticSeverity.Warning => NotificationOption2.Warning, DiagnosticSeverity.Error => NotificationOption2.Error, _ => throw new ArgumentException(nameof(element)), }); } private static Func<string, T> GetParser(string type) => type switch { nameof(Boolean) => // Try to map a boolean value. Either map it to true/false if we're a // CodeStyleOption<bool> or map it to the 0 or 1 value for an enum if we're // a CodeStyleOption<SomeEnumType>. v => Convert(bool.Parse(v)), nameof(Int32) => v => Convert(int.Parse(v)), nameof(String) => v => (T)(object)v, _ => throw new ArgumentException(nameof(type)), }; private static T Convert(bool b) { // If we had a bool and we wanted a bool, then just return this value. if (typeof(T) == typeof(bool)) { return (T)(object)b; } // Map booleans to the 1/0 value of the enum. return b ? (T)(object)1 : (T)(object)0; } private static T Convert(int i) { // We got an int, but we wanted a bool. Map 0 to false, 1 to true, and anything else to default. if (typeof(T) == typeof(bool)) { return (T)(object)(i == 1); } // If had an int and we wanted an enum, then just return this value. return (T)(object)(i); } public bool Equals(CodeStyleOption2<T>? other) { return other is not null && EqualityComparer<T>.Default.Equals(Value, other.Value) && Notification == other.Notification; } public override bool Equals(object? obj) => obj is CodeStyleOption2<T> option && Equals(option); public override int GetHashCode() => unchecked((Notification.GetHashCode() * (int)0xA5555529) + EqualityComparer<T>.Default.GetHashCode(Value!)); } |